/**
 * Copyright © 2011 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */

package org.creativor.rayson.client.shell;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.HashMap;
import org.creativor.rayson.api.RpcProtocol;
import org.creativor.rayson.api.ServiceRegistration;
import org.creativor.rayson.client.Rayson;
import org.creativor.rayson.exception.IllegalServiceException;
import org.creativor.rayson.exception.RpcException;
import org.creativor.rayson.protocol.ServerProtocol;
import com.google.gson.Gson;

/**
 * A server agent presents one RPC server.
 * 
 * @author Nick Zhang
 */
class ServerAgent
{
	private class ServiceLocal
	{
		private HashMap<String, Method> methods = new HashMap<String, Method>();
		private HashMap<Method, Class<RpcProtocol>> protocols =
				new HashMap<Method, Class<RpcProtocol>>();
		
		/**
		 * @param protocolClassName
		 */
		public void addProtocol(String protocolClassName)
		{
			Class klass = null;
			Class<RpcProtocol> protocol;
			try
			{
				klass = Class.forName(protocolClassName);
				protocol = klass;
			} catch (Throwable e)
			{
				return;
			}
			for (Method method : klass.getMethods())
			{
				methods.put(method.getName(), method);
				protocols.put(method, protocol);
			}
		}
		
		/**
		 * @param serviceName
		 * @param methodName
		 * @param commandParameters
		 * @return
		 * @throws RpcInvokeException
		 */
		public String invoke(String serviceName, String methodName, String commandParameters)
				throws RpcInvokeException
		{
			Method method = methods.get(methodName);
			if (method == null) throw new RpcInvokeException("Can not find method: " + methodName);
			Class<RpcProtocol> protocol;
			protocol = protocols.get(method);
			if (protocol == null)
				throw new RpcInvokeException("Can not find protocol class associated with: "
						+ method.toString());
			RpcProtocol proxy;
			try
			{
				proxy = Rayson.createProxy(serviceName, protocol, serverAddress);
			} catch (IllegalServiceException e)
			{
				throw new RpcInvokeException(e.getMessage());
			}
			String[] argJsons = GSON.fromJson(commandParameters, String[].class);
			Object[] args = new Object[argJsons.length];
			int i = 0;
			for (Class<?> parameterType : method.getParameterTypes())
			{
				args[i] = GSON.fromJson(argJsons[i], parameterType);
				i++;
			}
			Object result;
			try
			{
				result = method.invoke(proxy, args);
			} catch (Throwable e)
			{
				throw new RpcInvokeException(e.getMessage());
			}
			return GSON.toJson(result);
		}
	}
	
	private HashMap<String, ServiceLocal> registrations;
	
	private InetSocketAddress serverAddress;
	
	private static final Gson GSON = new Gson();
	
	/**
	 * @param serverAddress
	 */
	public ServerAgent(InetSocketAddress serverAddress)
	{
		this.serverAddress = serverAddress;
	}
	
	/**
	 * @param serviceRegistration
	 */
	private void initServiceLocal(ServiceRegistration serviceRegistration)
	{
		ServiceLocal serviceLocal = new ServiceLocal();
		registrations.put(serviceRegistration.getName(), serviceLocal);
		for (String protocolClass : serviceRegistration.getProtocols())
		{
			serviceLocal.addProtocol(protocolClass);
		}
	}
	
	/**
	 * @param serviceName
	 * @param methodName
	 * @param parameters
	 * @return
	 * @throws RpcInvokeException
	 */
	public String invoke(String serviceName, String methodName, String parameters)
			throws RpcInvokeException
	{
		try
		{
			tryLoadServiceRegistration();
		} catch (RpcException e)
		{
			throw new RpcInvokeException(e.getMessage());
		}
		
		ServiceLocal registrationLocal = registrations.get(serviceName);
		if (registrationLocal == null)
			throw new RpcInvokeException("Can not find service: " + serviceName);
		return registrationLocal.invoke(serviceName, methodName, parameters);
	}
	
	/**
	 * @throws RpcException
	 * 
	 */
	private void tryLoadServiceRegistration() throws RpcException
	{
		if (registrations == null)
		{
			registrations = new HashMap<String, ServerAgent.ServiceLocal>();
			ServerProtocol serverService = Rayson.getServerProxy(serverAddress);
			ServiceRegistration[] serviceRegistrations = serverService.list();
			for (ServiceRegistration serviceRegistration : serviceRegistrations)
			{
				initServiceLocal(serviceRegistration);
			}
		}
	}
	
}
